#include <stdio.h>
#include "run3.h"

int run3(BODY *root, SymbolTable *symboltable) {
	run3DECL_LIST(root->decl_list);
	run3STATEMENT_LIST(root->statement_list, symboltable, NULL);
	return run3Error;
}

void run3DECL_LIST(DECL_LIST *decl_list) {
	if (decl_list != NULL) {
		run3DECL(decl_list->declaration);
		run3DECL_LIST(decl_list->decl_list);
	}
}

void run3DECL(DECLARATION *declaration) {
	FUNCTION * func;
	BODY* body;
	if (declaration->kind == funcK) {
		func = declaration->val.function;
		body = func->body;
		run3DECL_LIST(body->decl_list);
		run3STATEMENT_LIST(body->statement_list, func->table, func->head);
	}
}

void run3STATEMENT_LIST(STATEMENT_LIST *statement_list, SymbolTable *symboltable, HEAD *head) {
	switch (statement_list->kind) {
	case statement_list_baseK:
		run3STATEMENT(statement_list->statement, symboltable, head);
		break;
	case statement_listK:
		run3STATEMENT_LIST(statement_list->statement_list, symboltable, head);
		if(statement_list->statement!=NULL){
			run3STATEMENT(statement_list->statement, symboltable, head);
		}
		break;
	}
}
void run3STATEMENT(STATEMENT *statement, SymbolTable *symboltable, HEAD *head) {
	EXPRESSION *expression;
	VARIABLE *variable;
	SYMBOL *symbol;
	OPT_LENGTH *opt_length;
	TYPE *type;
	OPT_ELSE *opt_else;
	switch (statement->kind) {
	case returnK:
		expression = statement->val.return_expression;
		run3EXPRESSION(expression, symboltable);
		if (run3COMPARE_RETURN(expression->type, head->type, symboltable->next, symboltable,initSymbolTable(),initSymbolTable()) == 1) {
			run3Error++;
			fprintf(stderr,	"Error:%d: return type is not the same as function(%s) type\n",	statement->lineno, head->id);
		}
		break;
	case writeK:
		run3EXPRESSION(statement->val.write_expression, symboltable);
		break;
	case allocateK:
		opt_length = statement->val.allocateE.opt_length;
		if (opt_length != 0) {
			type = run3EXPRESSION(opt_length->expression, symboltable);
			while(type->kind == type_idK){
				symbol = getSymbol(symboltable, type->val.id);
				type = symbol->val.type;
			}
			if (type->kind != intK) {
				run3Error++;
				fprintf(stderr,"Error:%d: length to allocate is not of type int\n",	statement->lineno);
			}
		}
		break;
	case variableK:
		variable = statement->val.variableE.variable;
		run3VARIABLE(variable, symboltable);
		expression = statement->val.variableE.expression;
		run3EXPRESSION(expression, symboltable);
		if (run3COMPARE_TYPES(variable->type, expression->type, symboltable,initSymbolTable(),initSymbolTable()) == 1) {
			run3Error++;
			fprintf(stderr, "Error:%d: assignment error\n", statement->lineno);
		}
		break;
	case ifK:
		expression = statement->val.ifE.expression;
		run3EXPRESSION(expression, symboltable);
		type = expression->type;;
		while(type->kind == type_idK){
			symbol = getSymbol(symboltable, type->val.id);
			type = symbol->val.type;
		}
		if (type->kind != boolK) {
			run3Error++;
			fprintf(stderr, "Error:%d: expression for if-statement not bool\n", statement->lineno);
		}
		run3STATEMENT(statement->val.ifE.statement, symboltable, head);
		opt_else = statement->val.ifE.opt_else;
		if (opt_else != 0) {
			run3STATEMENT(opt_else->statement, symboltable, head);
		}
		break;
	case whileK:
		expression = statement->val.whileE.expression;
		run3EXPRESSION(expression, symboltable);
		type = expression->type;
		while(type->kind == type_idK){
			symbol = getSymbol(symboltable, type->val.id);
			type = symbol->val.type;
		}
		if (type->kind != boolK) {
			run3Error++;
			fprintf(stderr, "Error:%d: expression for while-statement not bool\n", statement->lineno);
		}
		run3STATEMENT(statement->val.whileE.statement, symboltable, head);
		break;
	case statementlistK:
		run3STATEMENT_LIST(statement->val.statement_list, symboltable, head);
		break;
	};
}
TYPE *run3GETTYPE(TYPE *type, SymbolTable *symboltable){
	SYMBOL *symbol;
	while(type->kind == type_idK){
		symbol = getSymbol(symboltable, type->val.id);
		type = symbol->val.type;
	}
	return type;
}
TYPE *run3EXPRESSION(EXPRESSION *expression, SymbolTable *symboltable) {
	EXPRESSION *exp1;
	EXPRESSION *exp2;
	TYPE *type1;
	TYPE *type2;
	TERM *term;
	switch (expression->kind) {
	case timesK:
		exp1 = expression->val.EXP_times.left;
		exp2 = expression->val.EXP_times.right;
		type1 = run3GETTYPE(run3EXPRESSION(exp1, symboltable), symboltable);
		type2 = run3GETTYPE(run3EXPRESSION(exp2, symboltable), symboltable);
		if (type1->kind != intK || type2->kind != intK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n", expression->lineno);
		}
		return expression->type;
		break;
	case divK:
		exp1 = expression->val.EXP_div.left;
		exp2 = expression->val.EXP_div.right;
		type1 = run3GETTYPE(run3EXPRESSION(exp1, symboltable), symboltable);
		type2 = run3GETTYPE(run3EXPRESSION(exp2, symboltable), symboltable);
		if (type1->kind != intK || type2->kind != intK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",
					expression->lineno);
		}
		return expression->type;
		break;
	case plusK:
		exp1 = expression->val.EXP_plus.left;
		exp2 = expression->val.EXP_plus.right;
		type1 = run3GETTYPE(run3EXPRESSION(exp1, symboltable), symboltable);
		type2 = run3GETTYPE(run3EXPRESSION(exp2, symboltable), symboltable);
		if (type1->kind != intK || type2->kind != intK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",
					expression->lineno);
		}
		return expression->type;
		break;
	case minusK:
		exp1 = expression->val.EXP_minus.left;
		exp2 = expression->val.EXP_minus.right;
		type1 = run3GETTYPE(run3EXPRESSION(exp1, symboltable), symboltable);
		type2 = run3GETTYPE(run3EXPRESSION(exp2, symboltable), symboltable);
		if (type1->kind != intK || type2->kind != intK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",
					expression->lineno);
		}
		return expression->type;
		break;
	case orK:
		exp1 = expression->val.EXP_div.left;
		exp2 = expression->val.EXP_div.right;
		type1 = run3GETTYPE(run3EXPRESSION(exp1, symboltable), symboltable);
		type2 = run3GETTYPE(run3EXPRESSION(exp2, symboltable), symboltable);
		type1 = run3EXPRESSION(exp1, symboltable);
		type2 = run3EXPRESSION(exp2, symboltable);
		if (type1->kind != boolK || type2->kind != boolK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",
					expression->lineno);
		}
		return expression->type;
		break;
	case andK:
		exp1 = expression->val.EXP_and.left;
		exp2 = expression->val.EXP_and.right;
		type1 = run3GETTYPE(run3EXPRESSION(exp1, symboltable), symboltable);
		type2 = run3GETTYPE(run3EXPRESSION(exp2, symboltable), symboltable);
		if (type1->kind != boolK || type2->kind != boolK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",
					expression->lineno);
		}
		return expression->type;
		break;
	case leqK:
		exp1 = expression->val.EXP_leq.left;
		exp2 = expression->val.EXP_leq.right;
		type1 = run3EXPRESSION(exp1, symboltable);
		type2 = run3EXPRESSION(exp2, symboltable);
		if (type1->kind != intK || type2->kind != intK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",
					expression->lineno);
		}
		return expression->type;
		break;
	case geqK:
		exp1 = expression->val.EXP_geq.left;
		exp2 = expression->val.EXP_geq.right;
		type1 = run3GETTYPE(run3EXPRESSION(exp1, symboltable), symboltable);
		type2 = run3GETTYPE(run3EXPRESSION(exp2, symboltable), symboltable);
		if (type1->kind != intK || type2->kind != intK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",
					expression->lineno);
		}
		return expression->type;
		break;
	case leK:
		exp1 = expression->val.EXP_lessthan.left;
		exp2 = expression->val.EXP_lessthan.right;
		type1 = run3GETTYPE(run3EXPRESSION(exp1, symboltable), symboltable);
		type2 = run3GETTYPE(run3EXPRESSION(exp2, symboltable), symboltable);
		if (type1->kind != intK || type2->kind != intK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",
					expression->lineno);
		}
		return expression->type;
		break;
	case grK:
		exp1 = expression->val.EXP_greaterthan.left;
		exp2 = expression->val.EXP_greaterthan.right;
		type1 = run3GETTYPE(run3EXPRESSION(exp1, symboltable), symboltable);
		type2 = run3GETTYPE(run3EXPRESSION(exp2, symboltable), symboltable);
		if (type1->kind != intK || type2->kind != intK) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",
					expression->lineno);
		}
		return expression->type;
		break;
	case eqK:
		exp1 = expression->val.EXP_eq.left;
		exp2 = expression->val.EXP_eq.right;
		type1 = run3EXPRESSION(exp1, symboltable);
		type2 = run3EXPRESSION(exp2, symboltable);
		if (run3COMPARE_TYPES(type1, type2, symboltable,initSymbolTable(),initSymbolTable()) == 1) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",	expression->lineno);
		}
		return expression->type;
		break;
	case neqK:
		exp1 = expression->val.EXPneq.left;
		exp2 = expression->val.EXPneq.right;
		type1 = run3EXPRESSION(exp1, symboltable);
		type2 = run3EXPRESSION(exp2, symboltable);
		if (run3COMPARE_TYPES(type1, type2, symboltable, initSymbolTable(), initSymbolTable()) == 1) {
			run3Error++;
			fprintf(stderr, "Error:%d: incompatible type for expression\n",	expression->lineno);
		}
		return expression->type;
		break;
	case termK:
		term = expression->val.term;
		expression->type = run3TERM(expression->val.term, symboltable);
		return term->type;
		break;
	}
	return NULL;
}
TYPE *run3VARIABLE(VARIABLE *variable, SymbolTable *symboltable) {
	TYPE *type;
	SYMBOL *symbol;
	switch (variable->kind) {
	case var_idK:
		return variable->type;
		break;
	case var_expK:
		type = run3GETTYPE(run3VARIABLE(variable->val.var_expE.variable, symboltable),symboltable);
		if (type->kind != arrayK) {
			run3Error++;
			fprintf(stderr, "Error:%d: variable is not an array \n",
					variable->lineno);
		}
		type = run3GETTYPE(run3EXPRESSION(variable->val.var_expE.expression, symboltable),symboltable);
		if (type->kind != intK) {
			run3Error++;
			fprintf(stderr, "Error:%d: could not find place in array(not int)\n",variable->lineno);
		}
		return variable->type;
		break;
	case var_dotK:
		type = run3GETTYPE(run3VARIABLE(variable->val.var_dotE.variable, symboltable),symboltable);
		if (type->kind == recordK) {
			symbol = getSymbol(type->val.recordE.table,
					variable->val.var_dotE.id);
			type = symbol->val.var_type->type;
			return type;
		} else {
			run3Error++;
			fprintf(stderr, "Error:%d: variable is not a record\n", variable->lineno);
		}
		return variable->type;
		break;
	}
	return NULL;
}
int run3COMPARE_FUNC(EXP_LIST *exp_list, VAR_DECL_LIST *var_decl_list, SymbolTable *symboltable) {
	VAR_TYPE *var_type;
	EXPRESSION *expression;
	if (exp_list->kind == exp_list_baseK && var_decl_list->kind == var_decl_list_baseK) {
		expression = exp_list->expression;
		var_type = var_decl_list->var_type;
		return run3COMPARE_TYPES(expression->type, var_type->type, symboltable, initSymbolTable(),initSymbolTable());
	} else {
		var_type = var_decl_list->var_type;
		if (exp_list->kind == exp_listK && var_decl_list->kind == var_decl_listK) {
			expression = exp_list->expression;
			var_type = var_decl_list->var_type;
			if (run3COMPARE_TYPES(expression->type, var_type->type, symboltable, initSymbolTable(), initSymbolTable()) == 0) {
				return run3COMPARE_FUNC(exp_list->exp_list, var_decl_list->var_decl_list, symboltable);
			} else {
				return 1;
			}
		}
		return 1;
	}
	return 1;
}
TYPE *run3TERM(TERM *term, SymbolTable *symboltable) {
	TYPE *type;
	ACT_LIST *act_list;
	SYMBOL *symbol;
	HEAD *head;
	PAR_DECL_LIST *par_decl_list;
	switch (term->kind) {
	case term_varK:
		return run3VARIABLE(term->val.variable, symboltable);
		break;
	case term_idK:
		act_list = term->val.term_idE.act_list;
		symbol = getSymbol(symboltable, term->val.term_idE.id);
		if (symbol != NULL) {
			if (symbol->kind == functionSK) {
				head = symbol->val.function->head;
				if (act_list != 0) {
					if (head->par_decl_list != 0) {
						par_decl_list = head->par_decl_list;
						if (run3COMPARE_FUNC(act_list->exp_list,par_decl_list->var_decl_list, symboltable) == 1) {
							run3Error++;
							fprintf(stderr,"Error:%d: incorrect parameters for function '%s'\n",term->lineno, head->id);
						}
					} else {
						run3Error++;
						fprintf(
								stderr,
								"Error:%d: not correct number of parameters for '%s'\n",
								term->lineno, head->id);
					}
				} else {
					if (head->par_decl_list != 0) {
						run3Error++;
						fprintf(
								stderr,
								"Error:%d: not correct number of parameters for '%s'\n",
								term->lineno, head->id);
					}
				}
			} else {
				run3Error++;
				fprintf(stderr, "Error:%d: function does not exist %s\n",
						term->lineno, symbol->name);
			}
		} else {
			run3Error++;
			fprintf(stderr, "Error:%d: function does not exist\n", term->lineno);
		}
		break;
	case term_expK:
		return run3EXPRESSION(term->val.expression, symboltable);
		break;
	case term_notK:
		type = run3GETTYPE(run3TERM(term->val.term, symboltable),symboltable);
		if (type->kind != boolK) {
			run3Error++;
			fprintf(stderr, "Error:%d: negation on something not bool\n",
					term->lineno);
		}
		break;
	case term_act_expK:
		return run3EXPRESSION(term->val.act_expression, symboltable);
		break;
	case term_numK:
		break;
	case term_trueK:
		break;
	case term_falseK:
		break;
	case term_nullK:
		break;
	}
	return term->type;
}
int run3COMPARE_RETURN(TYPE *exp_type, TYPE *head_type, SymbolTable *next, SymbolTable *symboltable, SymbolTable *compareTable1, SymbolTable *compareTable2){
	TYPE *exp_old;
	TYPE *head_old;
	SYMBOL *symbol;
	if(exp_type==NULL){
		head_type = run3GETTYPE(head_type, next);
		if(head_type->kind == arrayK || head_type->kind == recordK){
			return 0;
		}else{
			return 1;
		}
	}
	if(exp_type->kind == head_type->kind){
		switch(exp_type->kind){
		case type_idK:
			while(exp_type->kind == type_idK){
				exp_old = exp_type;
				symbol = getSymbol(symboltable, exp_type->val.id);
				exp_type = symbol->val.type;
			}
			while(head_type->kind == type_idK){
				head_old = head_type;
				symbol = getSymbol(next, head_type->val.id);
				head_type = symbol->val.type;
			}
			if(putSymbolType(compareTable1,exp_old->val.id, NULL)==NULL){
				// Vi har set dette ID før! derfor skal vi også have set det andet id
				if(putSymbolType(compareTable2, head_old->val.id, NULL)==NULL){
					return 0;
				}else{
					return 1;
				}
			}else{
				// Ellers må vi heller ikke have set det andet ID før.
				if(putSymbolType(compareTable2,head_old->val.id, NULL)!=NULL){
					return run3COMPARE_RETURN(exp_type,head_type,next,symboltable, compareTable1, compareTable2);
				}else{
					return 1;
				}
			}
			break;
		case intK:
			return 0;
			break;
		case boolK:
			return 0;
			break;
		case arrayK:
			return run3COMPARE_RETURN(exp_type->val.type, head_type->val.type, next, symboltable, compareTable1, compareTable2);
			break;
		case recordK:
			return run3COMPARE_RETURN_RECORD(exp_type->val.recordE.var_decl_list, head_type->val.recordE.var_decl_list,next, symboltable, compareTable1, compareTable2);
			break;
		}
	}else{
		if(exp_type->kind == type_idK || head_type->kind == type_idK){
			return run3COMPARE_RETURN(run3GETTYPE(exp_type, symboltable),run3GETTYPE(head_type, next),next,symboltable,compareTable1,compareTable2);
		}else{
			return 1;
		}
	}
	return 1;
}
int run3COMPARE_RETURN_RECORD(VAR_DECL_LIST *record1, VAR_DECL_LIST *record2, SymbolTable *next, SymbolTable *symboltable, SymbolTable *compareTable1, SymbolTable *compareTable2) {
	VAR_TYPE *r1;
	VAR_TYPE *r2;
	if (record1->kind == record2->kind) {
		r1 = record1->var_type;
		r2 = record2->var_type;
		if (run3COMPARE_RETURN(r1->type, r2->type, next, symboltable, compareTable1, compareTable2) == 0) {
			if (record1->kind == var_decl_list_baseK) {
				return 0;
			} else {
				return run3COMPARE_RETURN_RECORD(record1->var_decl_list,record2->var_decl_list, next, symboltable, compareTable1, compareTable2);
			}
		}
	} else {
		return 1;
	}
	return 1;
}
int run3COMPARE_TYPES(TYPE *type1, TYPE *type2, SymbolTable *symboltable, SymbolTable *compareTable1, SymbolTable *compareTable2){
	TYPE *type1_old;
	TYPE *type2_old;
	SYMBOL *symbol;
	if(type1==NULL){
		type2 = run3GETTYPE(type2, symboltable);
		if(type2->kind == arrayK || type2->kind == recordK){
			return 0;
		}else{
			return 1;
		}
	}
	if(type2==NULL){
		type1 = run3GETTYPE(type1, symboltable);
		if(type1->kind == arrayK || type1->kind == recordK){
			return 0;
		}else{
			return 1;
		}
	}
	if (type1->kind == type2->kind) {
		switch (type1->kind) {
		case type_idK:
			while(type1->kind == type_idK){
				type1_old = type1;
				symbol = getSymbol(symboltable, type1->val.id);
				type1 = symbol->val.type;
			}
			while(type2->kind == type_idK){
				type2_old = type2;
				symbol = getSymbol(symboltable, type2->val.id);
				type2 = symbol->val.type;
			}
			if(putSymbolType(compareTable1,type1_old->val.id, NULL)==NULL){
				// Vi har set dette ID før! derfor skal vi også have set det andet id
				if(putSymbolType(compareTable2, type2_old->val.id, NULL)==NULL){
					return 0;
				}else{
					return 1;
				}
			}else{
				// Ellers må vi heller ikke have set det andet ID før.
				if(putSymbolType(compareTable2,type2_old->val.id, NULL)!=NULL){
					return run3COMPARE_TYPES(type1,type2,symboltable, compareTable1, compareTable2);
				}else{
					return 1;
				}
			}
			break;
		case intK:
			return 0;
			break;
		case boolK:
			return 0;
			break;
		case arrayK:
			return run3COMPARE_TYPES(type1->val.type, type2->val.type, symboltable, compareTable1, compareTable2);
			break;
		case recordK:
			return run3COMPARE_VAR_DECL_LIST(type1->val.recordE.var_decl_list, type2->val.recordE.var_decl_list, symboltable, compareTable1, compareTable2);
			break;
		}
	} else {
		if(type1->kind == type_idK || type2->kind == type_idK){
			return run3COMPARE_TYPES(run3GETTYPE(type1, symboltable),run3GETTYPE(type2, symboltable),symboltable, compareTable1, compareTable2);
		}else{
			return 1;
		}
	}
	return 1;
}

int run3COMPARE_VAR_DECL_LIST(VAR_DECL_LIST *record1, VAR_DECL_LIST *record2, SymbolTable *symboltable, SymbolTable *compareTable1, SymbolTable *compareTable2) {
	VAR_TYPE *r1;
	VAR_TYPE *r2;
	if (record1->kind == record2->kind) {
		r1 = record1->var_type;
		r2 = record2->var_type;
		if (run3COMPARE_TYPES(r1->type, r2->type, symboltable, compareTable1, compareTable2) == 0) {
			if (record1->kind == var_decl_list_baseK) {
				return 0;
			} else {
				return run3COMPARE_VAR_DECL_LIST(record1->var_decl_list,record2->var_decl_list, symboltable, compareTable1, compareTable2);
			}
		}
	} else {
		return 1;
	}
	return 1;
}
